#!/bin/sh

PREREQ="cryptroot-prepare"

#
# Standard initramfs preamble
#
prereqs()
{
	# Make sure that cryptroot is run last in local-top
	for req in $(dirname $0)/*; do
		script=${req##*/}
		if [ $script != cryptroot ]; then
			echo $script
		fi
	done
}

case $1 in
prereqs)
	prereqs
	exit 0
	;;
esac

# source for log_*_msg() functions, see LP: #272301
. /scripts/functions

#
# Helper functions
#
message()
{
	if [ -x /bin/plymouth ] && plymouth --ping; then
		plymouth message --text="$@"
	else
		echo "$@" >&2
	fi
	return 0
}

udev_settle()
{
	# Wait for udev to be ready, see https://launchpad.net/bugs/85640
	if command -v udevadm >/dev/null 2>&1; then
		udevadm settle --timeout=30
	elif command -v udevsettle >/dev/null 2>&1; then
		udevsettle --timeout=30
	fi
	return 0
}

parse_options()
{
	local cryptopts
	cryptopts="$1"

	if [ -z "$cryptopts" ]; then
		return 1
	fi

	# Defaults
	cryptcipher=aes-cbc-essiv:sha256
	cryptsize=256
	crypthash=ripemd160
	crypttarget=cryptroot
	cryptsource=""
	cryptheader=""
	cryptlvm=""
	cryptkeyscript=""
	cryptkey="" # This is only used as an argument to an eventual keyscript
	cryptkeyslot=""
	crypttries=3
	crypttcrypt=""
	cryptveracrypt=""
	cryptrootdev=""
	cryptdiscard=""
	CRYPTTAB_OPTIONS=""

	local IFS=" ,"
	for x in $cryptopts; do
		case $x in
		hash=*)
			crypthash=${x#hash=}
			;;
		size=*)
			cryptsize=${x#size=}
			;;
		cipher=*)
			cryptcipher=${x#cipher=}
			;;
		target=*)
			crypttarget=${x#target=}
			export CRYPTTAB_NAME="$crypttarget"
			;;
		source=*)
			cryptsource=${x#source=}
			if [ ${cryptsource#UUID=} != $cryptsource ]; then
				cryptsource="/dev/disk/by-uuid/${cryptsource#UUID=}"
			elif [ ${cryptsource#LABEL=} != $cryptsource ]; then
				cryptsource="/dev/disk/by-label/$(printf '%s' "${cryptsource#LABEL=}" | sed 's,/,\\x2f,g')"
			fi
			export CRYPTTAB_SOURCE="$cryptsource"
			;;
		header=*)
			cryptheader=${x#header=}
			if [ ! -e "$cryptheader" ] && [ -e "/conf/conf.d/cryptheader/$cryptheader" ]; then
				cryptheader="/conf/conf.d/cryptheader/$cryptheader"
			fi
			export CRYPTTAB_HEADER="$cryptheader"
			;;
		lvm=*)
			cryptlvm=${x#lvm=}
			;;
		keyscript=*)
			cryptkeyscript=${x#keyscript=}
			;;
		key=*)
			if [ "${x#key=}" != "none" ]; then
				cryptkey=${x#key=}
			fi
			export CRYPTTAB_KEY="$cryptkey"
			;;
		keyslot=*)
			cryptkeyslot=${x#keyslot=}
			;;
		tries=*)
			crypttries="${x#tries=}"
			case "$crypttries" in
			  *[![:digit:].]*)
				crypttries=3
				;;
			esac
			;;
		tcrypt)
			crypttcrypt="yes"
			;;
		veracrypt)
			cryptveracrypt="--veracrypt"
			;;
		rootdev)
			cryptrootdev="yes"
			;;
		discard)
			cryptdiscard="yes"
			;;
		esac
		PARAM="${x%=*}"
		if [ "$PARAM" = "$x" ]; then
			VALUE="yes"
		else
			VALUE="${x#*=}"
		fi
		CRYPTTAB_OPTIONS="$CRYPTTAB_OPTIONS $PARAM"
		eval export CRYPTTAB_OPTION_$PARAM="\"$VALUE\""
	done
	export CRYPTTAB_OPTIONS

	if [ -z "$cryptsource" ]; then
		message "cryptsetup ($crypttarget): source parameter missing"
		return 1
	fi
	return 0
}

activate_vg()
{
	# Sanity checks
	if [ ! -x /sbin/lvm ]; then
		message "cryptsetup ($crypttarget): lvm is not available"
		return 1
	fi

	# Detect and activate available volume groups
	/sbin/lvm vgscan
	/sbin/lvm vgchange -a y --sysinit
	return $?
}

setup_mapping()
{
	local opts count cryptopen cryptremove NEWROOT
	opts="$1"

	if [ -z "$opts" ]; then
		return 0
	fi

	parse_options "$opts" || return 1

	if [ -z "$cryptkeyscript" ]; then
		if [ ${cryptsource#/dev/disk/by-uuid/} != $cryptsource ]; then
			# UUIDs are not very helpful
			diskname="$crypttarget"
		else
			diskname="$cryptsource ($crypttarget)"
		fi
		cryptkeyscript="/lib/cryptsetup/askpass"
		cryptkey="Please unlock disk $diskname: "
	elif ! type "$cryptkeyscript" >/dev/null; then
		message "cryptsetup ($crypttarget): error - script \"$cryptkeyscript\" missing"
		return 1
	fi

	if [ "$cryptkeyscript" = "cat" ] && [ "${cryptkey#/root/}" != "$cryptkey" ]; then
		# skip the mapping if the root FS is not mounted yet
		sed -rn 's/^\s*[^#]\S*\s+(\S+)\s.*/\1/p' /proc/mounts | grep -Fxq "$rootmnt" || return 1
		# substitute the "/root" prefix by the real root FS mountpoint otherwise
		cryptkey="${rootmnt}/${cryptkey#/root/}"
	fi

	if [ -n "$cryptheader" ] && ! type "$cryptheader" >/dev/null; then
		message "cryptsetup ($crypttarget): error - LUKS header \"$cryptheader\" missing"
		return 1
	fi

	# The same target can be specified multiple times
	# e.g. root and resume lvs-on-lvm-on-crypto
	if [ -e "/dev/mapper/$crypttarget" ]; then
		return 0
	fi

	modprobe -q dm_crypt

	# Make sure the cryptsource device is available
	if [ ! -e $cryptsource ]; then
		activate_vg
	fi

	# If the encrypted source device hasn't shown up yet, give it a
	# little while to deal with removable devices

	# the following lines below have been taken from
	# /usr/share/initramfs-tools/scripts/local, as suggested per
	# https://launchpad.net/bugs/164044
	if [ ! -e "$cryptsource" ]; then
		log_begin_msg "Waiting for encrypted source device..."

		# Default delay is 180s
		if [ -z "${ROOTDELAY}" ]; then
			slumber=180
		else
			slumber=${ROOTDELAY}
		fi

		slumber=$(( ${slumber} * 10 ))
		while [ ! -e "$cryptsource" ]; do
			# retry for LVM devices every 10 seconds
			if [ ${slumber} -eq $(( ${slumber}/100*100 )) ]; then
				activate_vg
			fi

			/bin/sleep 0.1
			slumber=$(( ${slumber} - 1 ))
			[ ${slumber} -gt 0 ] || break
		done

		if [ ${slumber} -gt 0 ]; then
			log_end_msg 0
		else
			log_end_msg 1 || true
		fi
 	fi
	udev_settle

	# We've given up, but we'll let the user fix matters if they can
	if [ ! -e "${cryptsource}" ]; then
		
		echo "  ALERT! ${cryptsource} does not exist."
		echo "	Check cryptopts=source= bootarg: cat /proc/cmdline"
		echo "	or missing modules, devices: cat /proc/modules; ls /dev"
		panic -r "Dropping to a shell. Will skip ${cryptsource} if you can't fix."
	fi

	if [ ! -e "${cryptsource}" ]; then
		return 1
	fi


	# Prepare commands
	cryptopen="/sbin/cryptsetup -T 1"
	if [ "$cryptdiscard" = "yes" ]; then
		cryptopen="$cryptopen --allow-discards"
	fi
	if [ -n "$cryptheader" ]; then
		cryptopen="$cryptopen --header=$cryptheader"
	fi
	if [ -n "$cryptkeyslot" ]; then
		cryptopen="$cryptopen --key-slot=$cryptkeyslot"
	fi
	if /sbin/cryptsetup isLuks ${cryptheader:-$cryptsource} >/dev/null 2>&1; then
		cryptopen="$cryptopen open --type luks $cryptsource $crypttarget --key-file=-"
	elif [ "$crypttcrypt" = "yes" ]; then
		cryptopen="$cryptopen open --type tcrypt $cryptveracrypt $cryptsource $crypttarget"
	else
		cryptopen="$cryptopen -c $cryptcipher -s $cryptsize -h $crypthash open --type plain $cryptsource $crypttarget --key-file=-"
	fi
	cryptremove="/sbin/cryptsetup remove $crypttarget"
	NEWROOT="/dev/mapper/$crypttarget"

	# Try to get a satisfactory password $crypttries times
	count=0
	while [ $crypttries -le 0 ] || [ $count -lt $crypttries ]; do
		export CRYPTTAB_TRIED="$count"
		count=$(( $count + 1 ))

		if [ ! -e "$NEWROOT" ]; then
			if ! crypttarget="$crypttarget" cryptsource="$cryptsource" \
			     $cryptkeyscript "$cryptkey" | $cryptopen; then
				message "cryptsetup ($crypttarget): cryptsetup failed, bad password or options?"
				continue
			fi
		fi

		if [ ! -e "$NEWROOT" ]; then
			message "cryptsetup ($crypttarget): unknown error setting up device mapping"
			return 1
		fi

		#FSTYPE=''
		#eval $(fstype < "$NEWROOT")
		FSTYPE="$(/sbin/blkid -s TYPE -o value "$NEWROOT")"

		# See if we need to setup lvm on the crypto device
		#if [ "$FSTYPE" = "lvm" ] || [ "$FSTYPE" = "lvm2" ]; then
		if [ "$FSTYPE" = "LVM_member" ] || [ "$FSTYPE" = "LVM2_member" ]; then
			if [ -z "$cryptlvm" ]; then
				message "cryptsetup ($crypttarget): lvm fs found but no lvm configured"
				return 1
			elif ! activate_vg; then
				# disable error message, LP: #151532
				#message "cryptsetup ($crypttarget): failed to setup lvm device"
				return 1
			fi

			# Apparently ROOT is already set in /conf/param.conf for
			# flashed kernels at least. See bugreport #759720.
			if [ -f /conf/param.conf ] && grep -q "^ROOT=" /conf/param.conf; then
				NEWROOT=$(sed -n 's/^ROOT=//p' /conf/param.conf)
			else
				NEWROOT=${cmdline_root:-/dev/mapper/$cryptlvm}
				if [ "$cryptrootdev" = "yes" ]; then
					# required for lilo to find the root device
					echo "ROOT=$NEWROOT" >>/conf/param.conf
				fi
			fi
			#eval $(fstype < "$NEWROOT")
			FSTYPE="$(/sbin/blkid -s TYPE -o value "$NEWROOT")"
		fi

		#if [ -z "$FSTYPE" ] || [ "$FSTYPE" = "unknown" ]; then
		if [ -z "$FSTYPE" ]; then
			message "cryptsetup ($crypttarget): unknown fstype, bad password or options?"
			udev_settle
			$cryptremove
			continue
		fi

		# decrease $count by 1, apparently last try was successful.
		count=$(( $count - 1 ))

		message "cryptsetup ($crypttarget): set up successfully"
		break
	done

	failsleep=60 # make configurable later?

	if [ "$cryptrootdev" = "yes" ] && [ $crypttries -gt 0 ] && [ $count -ge $crypttries ]; then
		message "cryptsetup ($crypttarget): maximum number of tries exceeded"
		message "cryptsetup: going to sleep for $failsleep seconds..."
		sleep $failsleep
		exit 1
	fi

	udev_settle
	return 0
}

#
# Begin real processing
#

# Do we have any kernel boot arguments?
cmdline_cryptopts=''
unset cmdline_root
for opt in $(cat /proc/cmdline); do
	case $opt in
	cryptopts=*)
		opt="${opt#cryptopts=}"
		if [ -n "$opt" ]; then
			if [ -n "$cmdline_cryptopts" ]; then
				cmdline_cryptopts="$cmdline_cryptopts $opt"
			else
				cmdline_cryptopts="$opt"
			fi
		fi
		;;
	root=*)
		opt="${opt#root=}"
		case $opt in
		/*) # Absolute path given. Not lilo major/minor number.
			cmdline_root=$opt
			;;
		*) # lilo major/minor number (See #398957). Ignore
		esac
		;;
	esac
done

if [ -n "$cmdline_cryptopts" ]; then
	# Call setup_mapping separately for each possible cryptopts= setting
	for cryptopt in $cmdline_cryptopts; do
		setup_mapping "$cryptopt"
	done
	exit 0
fi

# Do we have any settings from the /conf/conf.d/cryptroot file?
if [ -r /conf/conf.d/cryptroot ]; then
	while read mapping <&3; do
		setup_mapping "$mapping" 3<&-
	done 3< /conf/conf.d/cryptroot
fi

exit 0
